//[-------------------------------------------------------]
//[ Includes                                              ]
//[-------------------------------------------------------]
#include <QtCore/QTimer>
#include <QtGui/QApplication>
#include <QtGui/QGridLayout>
#include <QtXml/QXmlSimpleReader>

#include <osg/Depth>
#include <osg/PolygonOffset>
#include <osg/ShapeDrawable>
#include <osgDB/ReadFile>
#include <osgGA/CameraManipulator>
#include <osgGA/TerrainManipulator>
#include <osgGA/StateSetManipulator>
#include <osgQt/GraphicsWindowQt>
#include <osgViewer/ViewerEventHandlers>

#include "QtViewer.h"

#include "AreaNode.h"
#include "BuildingNode.h"
#include "CoordTransformer.h"
#include "InputEventHandler.h"
#include "OSMHandler.h"
#include "PointOfInterestNode.h"
#include "StreetNode.h"

#include "OSMNode.h"
#include "OSMWay.h"
#include "OSMRelation.h"

#include <osg/ComputeBoundsVisitor>
#include <osg/MatrixTransform>
#include <osg/PolygonMode>

#include <algorithm>

QtViewer::QtViewer() : QWidget()
, m_x(0.0)
, m_y(0.0),
m_transformer(new CoordTransformer)
{
    // Set threading model to single threaded
    setThreadingModel(osgViewer::CompositeViewer::SingleThreaded);
	
	m_configWidget = new ConfigWidget(this);
	m_configWidget->setBaseSize(QSize(200,600));
	m_configWidget->setSizePolicy(QSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed));
	m_configWidget->show();

	// Create view widget
    QWidget *viewWidget = createViewWidget(0, 0, 800, 600, NULL);
	viewWidget->setBaseSize(QSize(600,600));
	viewWidget->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding));

	// Set layout
	QHBoxLayout *layout = new QHBoxLayout;
	layout->setContentsMargins(1, 1, 1, 1);
	layout->addWidget(viewWidget, 0);
	layout->addWidget(m_configWidget, 1);
	setLayout(layout);

    // Create timer
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(update()));
    m_timer.start( 10 );

	parseOSMFile();
    buildSceneGraph();
	initCameraPosition();
}

void QtViewer::buildSceneGraph()
{
    QString addrCity;
    QString addrCountry;
    QString addrPostcode;
    QString addrStreet;
    QString addrSuburb;

    QString category;
    QString name;

    std::vector<int> alreadyProcessedWayRefs;
    
    // foreach relation
    for(int i = 0; i < m_osmHandler->m_osmRelations.size(); ++i)
    {
        // only multipolygons need to be processed here
        if(m_osmHandler->m_osmRelations[i].isMultipolygon())
        {
            QList<int> innerWayRefs = m_osmHandler->m_osmRelations[i].getInnerWays();
            QList<int> outerWayRefs = m_osmHandler->m_osmRelations[i].getOuterWays();

            // prepare inner vertices
            QList< osg::ref_ptr<osg::Vec3Array> > innerVertices;
            for(int j = 0; j < innerWayRefs.size(); ++j)
            {
                innerVertices.append(new osg::Vec3Array);
                collectVerticesForRelations(innerVertices[j].get(), innerWayRefs[j]);
            }

            // prepare outer vertices
            osg::ref_ptr<osg::Vec3Array> outerVertices = new osg::Vec3Array;
            for(int j = 0; j < outerWayRefs.size(); ++j)
            {
                collectVerticesForRelations(outerVertices, outerWayRefs[j]);
            }

            // heuristic to choose category
            bool foundMatchingCategory = false;
            for(int i = 0; i < outerWayRefs.size(); ++i)
            {
                if(foundMatchingCategory)
                {
                    break;
                }

                for(int j = 0; j < m_osmHandler->m_osmWays.size(); ++j)
                {
                    if(m_osmHandler->m_osmWays[j].getId() == outerWayRefs.at(i))
                    {
                        // check if building
                        if(m_osmHandler->m_osmWays[j].isBuilding(name,
                            addrCity,
                            addrCountry,
                            addrPostcode,
                            addrStreet,
                            addrSuburb))
                        {
                            foundMatchingCategory = true;
                            // do not process buildings here
                            break;
                        }
                        // check if street area
                        else if(m_osmHandler->m_osmWays[j].isStreet(category, name))
                        {
                            foundMatchingCategory = true;
                            // do not process streets here
                            break;
                        }

                        // check if water area
                        else if(m_osmHandler->m_osmWays[j].isWaterArea(category, name))
                        {
                            if(category == "river")
                            {
                                // then this is not an area, but a linestrip
                                osg::ref_ptr<AreaNode> waterArea = new AreaNode(outerVertices, innerVertices, category, name, 1);
                                m_naturalGroup->addChild(waterArea.get());
                            }
                            else if(category == "canal")
                            {
                                // then this is not an area, but a linestrip
                                osg::ref_ptr<AreaNode> waterArea = new AreaNode(outerVertices, innerVertices, category, name, 2);
                                m_naturalGroup->addChild(waterArea.get());
                            }
                            else if(category == "stream")
                            {
                                // then this is not an area, but a linestrip
                                osg::ref_ptr<AreaNode> waterArea = new AreaNode(outerVertices, innerVertices, category, name, 3);
                                m_naturalGroup->addChild(waterArea.get());
                            }
                            else if(category == "drain" || category == "ditch")
                            {
                                // then this is not an area, but a linestrip
                                osg::ref_ptr<AreaNode> waterArea = new AreaNode(outerVertices, innerVertices, category, name, 4);
                                m_naturalGroup->addChild(waterArea.get());
                            }
                            else // water
                            {
                                osg::ref_ptr<AreaNode> waterArea = new AreaNode(outerVertices, innerVertices, category, name, 0);
                                m_naturalGroup->addChild(waterArea.get());
                            }
                            foundMatchingCategory = true;
                            alreadyProcessedWayRefs.push_back(outerWayRefs.at(i));
                            break;
                        }

                        // check if green area
                        else if(m_osmHandler->m_osmWays[j].isGreenArea(category, name))
                        {
                            osg::ref_ptr<AreaNode> greenArea;
                            if(category == "forest")
                            {
                                greenArea = new AreaNode(outerVertices, innerVertices, category, name, 5);
                            }
                            else
                            {
                                greenArea =  new AreaNode(outerVertices, innerVertices, category, name, 6);
                            }
                            m_naturalGroup->addChild(greenArea.get());
                            foundMatchingCategory = true;
                            alreadyProcessedWayRefs.push_back(outerWayRefs.at(i));
                            break;
                        }

                        // check if residential area
                        else if(m_osmHandler->m_osmWays[j].isResidential(category, name))
                        {
                            osg::ref_ptr<AreaNode> residentialArea = new AreaNode(outerVertices, innerVertices, category, name, 7);
                            m_naturalGroup->addChild(residentialArea.get());
                            foundMatchingCategory = true;
                            alreadyProcessedWayRefs.push_back(outerWayRefs.at(i));
                            break;
                        }
                    }
                }
            }
        }
    }
    
    // foreach way that was not processed before
    for(int i = 0; i < m_osmHandler->m_osmWays.size(); ++i)
    {
        int ref = m_osmHandler->m_osmWays[i].getId();
        auto it = std::find(alreadyProcessedWayRefs.begin(), alreadyProcessedWayRefs.end(), ref);
        if(it == alreadyProcessedWayRefs.end())
        {
            osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;

            // check if building
            if(m_osmHandler->m_osmWays[i].isBuilding(name,
                                                     addrCity,
                                                     addrCountry,
                                                     addrPostcode,
                                                     addrStreet,
                                                     addrSuburb))
            {
                collectVertices(vertices.get(), i);

                osg::ref_ptr<BuildingNode> buildings = new BuildingNode(vertices,
                                                                        name,
                                                                        addrCity,
                                                                        addrCountry,
                                                                        addrPostcode,
                                                                        addrStreet,
                                                                        addrSuburb,
                                                                        30.0f);
                m_buildingGroup->addChild(buildings.get());
            }

            // check if street
            else if(m_osmHandler->m_osmWays[i].isStreet(category, name))
            {
                collectVertices(vertices.get(), i);
                osg::ref_ptr<StreetNode> streets;
                if(category == "primary")
                {
                    streets = new StreetNode(vertices, name, 3); 
                }
                if(category == "secondary")
                {
                    streets = new StreetNode(vertices, name, 0);
                }
                else if(category == "path" || category == "footway" || category == "cycleway")
                {
                    streets = new StreetNode(vertices, name, 2);
                }
                else
                {
                    streets = new StreetNode(vertices, name, 1); // normal street
                }
                m_streetGroup->addChild(streets.get());
            }

            // check if water area
            else if(m_osmHandler->m_osmWays[i].isWaterArea(category, name))
            {
                collectVertices(vertices.get(), i);

                if(category == "river")
                {
                    // then this is not an area, but a linestrip
                    osg::ref_ptr<AreaNode> waterArea = new AreaNode(vertices, category, name, 1);
                    m_naturalGroup->addChild(waterArea.get());
                }
                else if(category == "canal")
                {
                    // then this is not an area, but a linestrip
                    osg::ref_ptr<AreaNode> waterArea = new AreaNode(vertices, category, name, 2);
                    m_naturalGroup->addChild(waterArea.get());
                }
                else if(category == "stream")
                {
                    // then this is not an area, but a linestrip
                    osg::ref_ptr<AreaNode> waterArea = new AreaNode(vertices, category, name, 3);
                    m_naturalGroup->addChild(waterArea.get());
                }
                else if(category == "drain" || category == "ditch")
                {
                    // then this is not an area, but a linestrip
                    osg::ref_ptr<AreaNode> waterArea = new AreaNode(vertices, category, name, 4);
                    m_naturalGroup->addChild(waterArea.get());
                }
                else // water
                {
                    osg::ref_ptr<AreaNode> waterArea = new AreaNode(vertices, category, name, 0);
                    m_naturalGroup->addChild(waterArea.get());
                }
            }

            // check if green area
            else if(m_osmHandler->m_osmWays[i].isGreenArea(category, name))
            {
                collectVertices(vertices.get(), i);
                osg::ref_ptr<AreaNode> greenArea;
                if(category == "forest")
                {
                    greenArea = new AreaNode(vertices, category, name, 5);
                }
                else
                {
                    greenArea = new AreaNode(vertices, category, name, 6);
                }
                m_naturalGroup->addChild(greenArea.get());
            }
            else if(m_osmHandler->m_osmWays[i].isResidential(category, name))
            {
                collectVertices(vertices.get(), i);
                osg::ref_ptr<AreaNode> residentialArea = new AreaNode(vertices, category, name, 7);
                m_naturalGroup->addChild(residentialArea.get());
            }
        }
    }

    // adding POIs
    double x,y;

    for(int i = 0; i < m_osmHandler->m_osmNodes.size(); ++i)
    {
        if(m_osmHandler->m_osmNodes[i].isRestaurant(category, name))
        {
            m_transformer->transformCoordsFromLatLong(m_osmHandler->m_osmNodes[i].getLatitude(),
                                                      m_osmHandler->m_osmNodes[i].getLongitude(),
                                                      x, y);
            osg::Vec3 pos(x, y, 0.0f);
            osg::ref_ptr<PointOfInterestNode> poiNode = new PointOfInterestNode(pos, 0);
            m_PoIGroup->addChild(poiNode.get());
        }
        else if(m_osmHandler->m_osmNodes[i].isMarina(category, name))
        {
            m_transformer->transformCoordsFromLatLong(m_osmHandler->m_osmNodes[i].getLatitude(),
                                                      m_osmHandler->m_osmNodes[i].getLongitude(),
                                                      x, y);
            osg::Vec3 pos(x, y, 0.0f);
            osg::ref_ptr<PointOfInterestNode> poiNode = new PointOfInterestNode(pos, 1);
            m_PoIGroup->addChild(poiNode.get());
        }  
    }
    buildTerrain();
}

void QtViewer::collectVerticesForRelations(osg::Vec3Array* vertices, int wayRef)
{
    for(int i = 0; i < m_osmHandler->m_osmWays.size(); ++i)
    {
        if(m_osmHandler->m_osmWays[i].getId() == wayRef)
        {
            collectVertices(vertices, i);
        }
    }
}

void QtViewer::collectVertices(osg::Vec3Array* vertices, int wayIdx)
{
    double x, y;

    // foreach nodeId per way
    for(int j = 0; j < m_osmHandler->m_osmWays[wayIdx].getNodeIds()->size(); ++j)
    {
        // find matching references
        unsigned int ref = m_osmHandler->m_osmWays[wayIdx].getNodeIds()->at(j);

        // foreach node
        for(int k = 0; k < m_osmHandler->m_osmNodes.size(); ++k)
        {
            if(m_osmHandler->m_osmNodes[k].getId() == ref)
            {
                // coordinate transform
                m_transformer->transformCoordsFromLatLong(m_osmHandler->m_osmNodes[k].getLatitude(),
                                                          m_osmHandler->m_osmNodes[k].getLongitude(),
                                                          x, y);

                vertices->push_back(osg::Vec3(x, y, 0.0f));
                break; // match found
            }
        }
    }
}

QWidget *QtViewer::createViewWidget(int x, int y, int w, int h, osg::Node *scene)
{
    // Setup window and display settings
    osg::DisplaySettings *ds = osg::DisplaySettings::instance().get();
    osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
    traits->windowName = "GeoVis 2013 - Übung #1";
    traits->windowDecoration = false;
    traits->x = x;
    traits->y = y;
    traits->width = w;
    traits->height = h;
    traits->doubleBuffer = true;
    traits->alpha = ds->getMinimumNumAlphaBits();
    traits->stencil = ds->getMinimumNumStencilBits();
    traits->sampleBuffers = ds->getMultiSamples();
    traits->samples = ds->getNumMultiSamples();

    // Create camera
    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    camera->setGraphicsContext(new osgQt::GraphicsWindowQt(traits.get()));
    camera->setClearColor(osg::Vec4(0.69f, 0.77f, 0.87f, 1.0f));
    camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
    camera->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 1.0f, 10000.0f);

	// create view and init the scene
	m_view = new osgViewer::View;
	m_view->setCamera(camera);
	initScene();

	addView(m_view);

	// Return Qt widget
    osgQt::GraphicsWindowQt* gw = dynamic_cast<osgQt::GraphicsWindowQt*>( camera->getGraphicsContext());
	QGLFormat format = gw->getGraphWidget()->format();
	format.setSamples(ds->getNumMultiSamples());
	gw->getGraphWidget()->setFormat(format);
    return gw ? gw->getGraphWidget() : NULL;
}

void QtViewer::initScene()
{
	osg::DisplaySettings::instance()->setNumMultiSamples(8);
	
	// root container for all scene things
    m_rootNode = new osg::MatrixTransform();
	
	// init EventHandler
	m_view->addEventHandler(new osgViewer::StatsHandler());
    m_view->addEventHandler(new osgGA::StateSetManipulator(m_view->getCamera()->getOrCreateStateSet()));
	m_view->setCameraManipulator(new osgGA::TerrainManipulator);

	m_view->addEventHandler(new InputEventHandler(this));

	// Create and add a distant light
    osg::ref_ptr<osg::Light> light = new osg::Light(0);
    light->setDirection(osg::Vec3(0.0,0.5,0.7));
    light->setAmbient(osg::Vec4(0.6,0.6,0.6,1.0));
    light->setDiffuse(osg::Vec4(0.7,0.7,0.7,1.0));
	light->setSpecular(osg::Vec4(1.0,1.0,1.0,1.0));
	light->setConstantAttenuation(0.001);
	
	osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource();
	lightSource->setLight(light);
	lightSource->setLocalStateSetModes(osg::StateAttribute::ON);
	
	osg::ref_ptr<osg::StateSet> states = m_rootNode->getOrCreateStateSet();
	lightSource->setStateSetModes(*states, osg::StateAttribute::ON);
	
	m_rootNode->addChild(lightSource);

	// 2D scene things	
	m_2DGroup = new osg::Group();

	m_terrainGroup = new osg::Group();
	m_streetGroup = new osg::Group();
	m_naturalGroup = new osg::Group();
	
    m_2DGroup->addChild(m_terrainGroup);
	m_2DGroup->addChild(m_naturalGroup);
	m_2DGroup->addChild(m_streetGroup);
	
	// disable z-test to fight z-fighting
	// !important! the render order of the m_2DGroup defines which items are visible
	osg::ref_ptr<osg::StateSet> stateSet = m_2DGroup->getOrCreateStateSet();
	osg::ref_ptr<osg::Depth> depth = new osg::Depth(osg::Depth::ALWAYS);
	stateSet->setAttributeAndModes(depth, osg::StateAttribute::ON);

	// 3D scene things
	m_3DGroup = new osg::Group();
	m_buildingGroup = new osg::Group();
	m_PoIGroup = new osg::Group();
	m_markerGroup = new osg::Group();
	m_3DGroup->addChild(m_buildingGroup);
	m_3DGroup->addChild(m_PoIGroup);
	m_3DGroup->addChild(m_markerGroup);

	// Add all nodes as checkBoxes in the ConfigWidget
	m_configWidget->atNodeAsCheckBox(m_terrainGroup, "Terrain");
	m_configWidget->atNodeAsCheckBox(m_naturalGroup, "Naturals");
	m_configWidget->atNodeAsCheckBox(m_streetGroup, "Streets");
	m_configWidget->atNodeAsCheckBox(m_buildingGroup, "Buildings");
	m_configWidget->atNodeAsCheckBox(m_PoIGroup, "Points of Interests");

	// add the 2D and 3D group to the root node
	m_rootNode->addChild(m_2DGroup);
	m_rootNode->addChild(m_3DGroup);

	m_view->setSceneData(m_rootNode);
}


/**
*  @brief
*    Called on paint event
*/
void QtViewer::paintEvent(QPaintEvent *event)
{
    frame();
}

void QtViewer::parseOSMFile()
{
    QString filePath("../osm/map.osm");
	QFile* file = new QFile(filePath);

	QXmlSimpleReader xmlReader;

	QXmlInputSource *source = new QXmlInputSource(file);

	m_osmHandler = new OSMHandler();

	xmlReader.setContentHandler(m_osmHandler);

	bool ok  = xmlReader.parse(source);
	QMap<unsigned int, QList<QPair<float,float> > > wayMap;
	if(ok)
	{
		std::cout << "parsing successful" << std::endl;
    }
}

void QtViewer::buildTerrain()
{
    osg::ComputeBoundsVisitor visitor;
    m_2DGroup->accept(visitor);
    osg::BoundingBox bbox = visitor.getBoundingBox();

    // create bounds
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;

    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back(osg::Vec3(bbox.xMax(), bbox.yMax(), 0.0f));
    vertices->push_back(osg::Vec3(bbox.xMax(), bbox.yMin(), 0.0f));
    vertices->push_back(osg::Vec3(bbox.xMin(), bbox.yMin(), 0.0f));
    vertices->push_back(osg::Vec3(bbox.xMin(), bbox.yMax(), 0.0f));
    
    geom->setVertexArray(vertices.get());

    osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
    colors->push_back(osg::Vec4(241.0f/255.0f, 238.0f/255.0f, 232.0f/255.0f, 1.0));
    geom->setColorArray(colors.get());
    geom->setColorBinding(osg::Geometry::BIND_OVERALL);

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
    normals->push_back(osg::Vec3(0.f, -1.f, 0.f));
    geom->setNormalArray(normals.get());
    geom->setNormalBinding(osg::Geometry::BIND_OVERALL);

    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));

    geode->addDrawable(geom.get());
    geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
    m_terrainGroup->addChild(geode.get());

    transformToRange01();
}


double QtViewer::computeDistance(osg::Vec3 start, osg::Vec3 end)
{
	double distance = 0.0;
	
	QString startLocString = QString("%1 %2 %3").arg(QString::number(start[0]),QString::number(start[1]),QString::number(start[2]));
	QString endLocString = QString("%1 %2 %3").arg(QString::number(end[0]),QString::number(end[1]),QString::number(end[2]));
	QString string = QString("The distance from the starting point at: %1 to the endpoint at: %2 is %3 meter").arg(startLocString, endLocString, QString::number(distance));
	m_configWidget->displayText(string);

	return distance;
}

// TODO
void QtViewer::normalizeCoords(double& x, double& y)
{
	if(m_x == 0.0 && m_y == 0.0)
	{
		m_x = x;
		m_y = y;
	}
	x-=m_x;
	y-=m_y;
}

void QtViewer::initCameraPosition()
{
	const osg::BoundingSphere& bs = m_rootNode->getBound();
	osg::Vec3 bsCenter = bs.center();
	float radius = bs.radius();
	osg::Vec3 eye = bsCenter - (osg::Vec3(0.0,1.0,-1.0)*radius);

	osg::ref_ptr<osgGA::CameraManipulator> manipulator = m_view->getCameraManipulator();
	if(manipulator != NULL)
	{
		manipulator->setHomePosition(eye,bs.center(),osg::Vec3(0.0,0.0,1.0));
		m_view->setCameraManipulator(manipulator);
	}
}

void QtViewer::onIntersectionInfo(QString message)
{
    m_configWidget->displayText(message);
}

void QtViewer::onSetMarker(osg::Vec3* pos)
{
    static int binaryCounter = 0;
    static osg::Vec3 startPos;
    static osg::Vec3 endPos;

    // transform back
    *pos = *pos * m_rootNode->getInverseMatrix();
    pos->set(pos->x(), pos->y(), 0.0f);


    // hack
    if(pos->x() <= 1.0)
    {
        std::cerr << "Wrong click\n";
        return;
    }

    if(binaryCounter == 2)
    {
        PointOfInterestNode* startNode = NULL;
        PointOfInterestNode* endNode = NULL;
        StreetNode* euclideanDistanceNode = NULL;

        for(unsigned int i = 0; i < m_PoIGroup->getNumChildren(); ++i)
        {
            const std::string& nodeName = m_PoIGroup->getChild(i)->getName();
            if(nodeName == "startmarker")
            {
                startNode = dynamic_cast<PointOfInterestNode*>(m_PoIGroup->getChild(i));
            }
            else if(nodeName == "endmarker")
            {
                endNode = dynamic_cast<PointOfInterestNode*>(m_PoIGroup->getChild(i));
            }
        }

        for(unsigned int i = 0; i < m_streetGroup->getNumChildren(); ++i)
        {
            const std::string& nodeName = m_streetGroup->getChild(i)->getName();
            if(nodeName == "euclidean distance")
            {
                euclideanDistanceNode = dynamic_cast<StreetNode*>(m_streetGroup->getChild(i));
            }
        }

        m_PoIGroup->removeChild(startNode);
        m_PoIGroup->removeChild(endNode);
        m_streetGroup->removeChild(euclideanDistanceNode);
        binaryCounter = 0;
    }

    ++binaryCounter;

    osg::ref_ptr<PointOfInterestNode> marker = NULL;
    if(binaryCounter == 1)
    {
        marker = new PointOfInterestNode(*pos, 2);
        marker->setName("startmarker");
        startPos = *pos;
    }
    else
    {
        marker = new PointOfInterestNode(*pos, 3);
        marker->setName("endmarker");
        endPos = *pos;

        // visualize euclidean distance
        osg::ref_ptr<osg::Vec3Array> euclideanDistance = new osg::Vec3Array;
        euclideanDistance->push_back(startPos);
        euclideanDistance->push_back(endPos);

        osg::ref_ptr<StreetNode> line = new StreetNode(euclideanDistance, "euclidean distance", 4);
        line->setName("euclidean distance");
        m_streetGroup->addChild(line.get());

        // transform back to lat long
        double latStart, longStart, latEnd, longEnd;

        m_transformer->transformCoordsToLatLong(startPos.x(), startPos.y(), latStart, longStart);
        m_transformer->transformCoordsToLatLong(endPos.x(), endPos.y(), latEnd, longEnd);

        // calculate distance in meter
        double distance = euclideanDistanceInMeters(latStart, longStart, latEnd, longEnd);
        std::stringstream sstream;
        sstream << "euclidean distance: " << distance << " meters";
        m_configWidget->displayText(sstream.str().c_str());

    }
    m_PoIGroup->addChild(marker.get());
    m_PoIGroup->dirtyBound();
}

void QtViewer::transformToRange01()
{
    osg::ComputeBoundsVisitor visitor;
    m_2DGroup->accept(visitor); // only 2DGroup
    osg::BoundingBox bbox = visitor.getBoundingBox();

    float centerX = (bbox.xMax() - bbox.xMin()) / 2.0f;
    float centerY = (bbox.yMax() - bbox.yMin()) / 2.0f;

    std::cout << "bbox: min(" << bbox.xMin() << ", " << bbox.yMin() << ")\n";
    std::cout << "bbox: max(" << bbox.xMax() << ", " << bbox.yMax() << ")\n";

    std::cout << "distanceX: " << centerX * 2 << std::endl;
    std::cout << "distanceY: " << centerY * 2 << std::endl;

    std::cout << "CenterX: " << centerX << std::endl;
    std::cout << "CenterY: " << centerY << std::endl;

    std::cout << "TranslateX: " << -(bbox.xMin() + centerX) << std::endl;
    std::cout << "TranslateY: " << -(bbox.yMin() + centerY) << std::endl;

    osg::Matrixd T = osg::Matrix::translate(osg::Vec3d(-bbox.center().x(), -bbox.center().y(), 0.0));
    osg::Matrixd Mirror = osg::Matrix::scale(osg::Vec3d(1.0, -1.0, 1.0));

    m_rootNode->setMatrix(T * Mirror);
}

double QtViewer::euclideanDistanceInMeters(double lat1, double long1, double lat2, double long2)
{
    double earthRadius = 3958.75; // miles
    double dLat = osg::DegreesToRadians(lat2 - lat1);
    double dLong = osg::DegreesToRadians(long2 - long1);
    double a = sin(dLat/2) * sin(dLat/2) + cos(osg::DegreesToRadians(lat1)) * cos(osg::DegreesToRadians(lat2)) * sin(dLong/2) * sin(dLong/2);
    double c = 2 * atan2(sqrt(a), sqrt(1-a));
    double distance = earthRadius * c;
    double distanceInMeters = distance * 1609.0;
    return distanceInMeters;
}